#include "EventManager.h"
#include "../UI/Elements/UIScrollPanel.h"
#include "../UI/Elements/UICombobox.h"
#include "../UI/Elements/UISlider.h"

template <typename T, class C>
bool isOfType(C* obj)
{
	return dynamic_cast<T*>(obj) != NULL;
}


EventManager::EventManager(UIView* view) : view(view)
{
}

EventManager::~EventManager()
{
}

bool EventManager::Intersect(UIElement* first, UIElement* second)
{
	return false;
}

bool EventManager::Intersect(UIElement* elem, POINT point)
{
	if (point.x < elem->Position.x + elem->Width && point.x > elem->Position.x &&
		point.y < elem->Position.y + elem->Height && point.y > elem->Position.y)
	{
		return true;
	}
	return false;
}

std::vector<UIElement*> EventManager::GetMousePointIntersectionTargets(std::vector<UIElement*>& elements, POINT point)
{
	std::vector<UIElement*> targets;
	for (UIElement* elem : elements)
	{
		if (!elem->Visible || !elem->IsEnabled()) continue;
		if (Intersect(elem, point)) targets.push_back(elem);

		if (isOfType<UIScrollPanel>(elem))
		{
			UIElement* scrollbar_handle = static_cast<UIScrollPanel*>(elem)->_get_scrollbar_handle_();
			scrollbar_handle->DepthIndex = static_cast<UIScrollPanel*>(elem)->DepthIndex + (elem->DepthIndex + 1);

			if (Intersect(scrollbar_handle, point))
				targets.push_back(scrollbar_handle);

			for (int i = 0; i < static_cast<UIScrollPanel*>(elem)->GetElementCount(); i++)
			{
				UIElement* scrollpnl_element = static_cast<UIScrollPanel*>(elem)->GetElement(i);
				if (Intersect(scrollpnl_element, point))
				{
					scrollpnl_element->DepthIndex = static_cast<UIScrollPanel*>(elem)->DepthIndex + (i + 2);
					targets.push_back(scrollpnl_element);
				}
			}
		}
		else if (isOfType<UICombobox>(elem))
		{
			UIScrollPanel* itemScrollPanel = static_cast<UICombobox*>(elem)->_get_item_panel_handle_();

			UIElement* scrollbar_handle = itemScrollPanel->_get_scrollbar_handle_();
			scrollbar_handle->DepthIndex = itemScrollPanel->DepthIndex + (elem->DepthIndex + 1);

			if (Intersect(scrollbar_handle, point))
				targets.push_back(scrollbar_handle);

			for (int i = 0; i < itemScrollPanel->GetElementCount(); i++)
			{
				UIElement* scrollpnl_element = itemScrollPanel->GetElement(i);
				if (Intersect(scrollpnl_element, point))
				{
					scrollpnl_element->DepthIndex = itemScrollPanel->DepthIndex + (i + 2);
					targets.push_back(scrollpnl_element);
				}
			}
		}
		else if (isOfType<UISlider>(elem))
		{
			UIElement* slider_knob_handle = static_cast<UISlider*>(elem)->_get_slider_knob_handle_();
			slider_knob_handle->DepthIndex = elem->DepthIndex + (elem->DepthIndex + 1);

			if (Intersect(slider_knob_handle, point))
				targets.push_back(slider_knob_handle);
		}
	}
	return targets;
}

std::vector<UIElement*> EventManager::GetMousePointIntersectionTargets(std::vector<UIElement*>& elements, POINT point, std::vector<UIElement*>& missed_targets)
{
	std::vector<UIElement*> targets;
	for (UIElement* elem : elements)
	{
		if (!elem->Visible || !elem->IsEnabled()) continue;
		if (Intersect(elem, point)) targets.push_back(elem);
		else missed_targets.push_back(elem);

		if (isOfType<UIScrollPanel>(elem))
		{
			UIElement* scrollbar_handle = static_cast<UIScrollPanel*>(elem)->_get_scrollbar_handle_();
			scrollbar_handle->DepthIndex = static_cast<UIScrollPanel*>(elem)->DepthIndex + (elem->DepthIndex + 1);

			if (Intersect(scrollbar_handle, point))
				targets.push_back(scrollbar_handle);
			else
				missed_targets.push_back(scrollbar_handle);

			for (int i = 0; i < static_cast<UIScrollPanel*>(elem)->GetElementCount(); i++)
			{
				if (Intersect(static_cast<UIScrollPanel*>(elem)->GetElement(i), point))
				{
					static_cast<UIScrollPanel*>(elem)->GetElement(i)->DepthIndex = static_cast<UIScrollPanel*>(elem)->DepthIndex + (i + 1);
					targets.push_back(static_cast<UIScrollPanel*>(elem)->GetElement(i));
				}
				else
					missed_targets.push_back(static_cast<UIScrollPanel*>(elem)->GetElement(i));
			}
		}
		else if (isOfType<UICombobox>(elem))
		{
			UIScrollPanel* itemScrollPanel = static_cast<UICombobox*>(elem)->_get_item_panel_handle_();

			UIElement* scrollbar_handle = itemScrollPanel->_get_scrollbar_handle_();
			scrollbar_handle->DepthIndex = itemScrollPanel->DepthIndex + (elem->DepthIndex + 1);

			if (Intersect(scrollbar_handle, point))
				targets.push_back(scrollbar_handle);

			for (int i = 0; i < itemScrollPanel->GetElementCount(); i++)
			{
				UIElement* scrollpnl_element = itemScrollPanel->GetElement(i);
				if (Intersect(scrollpnl_element, point))
				{
					scrollpnl_element->DepthIndex = itemScrollPanel->DepthIndex + (i + 2);
					targets.push_back(scrollpnl_element);
				}
				else
					missed_targets.push_back(itemScrollPanel->GetElement(i));
			}
		}
		else if (isOfType<UISlider>(elem))
		{
			UIElement* slider_knob_handle = static_cast<UISlider*>(elem)->_get_slider_knob_handle_();
			slider_knob_handle->DepthIndex = elem->DepthIndex + (elem->DepthIndex + 1);

			if (Intersect(slider_knob_handle, point))
				targets.push_back(slider_knob_handle);
			else
				missed_targets.push_back(slider_knob_handle);
		}
	}
	return targets;
}

UIElement* EventManager::GetTopMostElement(std::vector<UIElement*>& targets)
{
	UIElement* targetElement = nullptr;
	if (targets.size() == 1) { targetElement = targets.at(0); }
	else if (targets.size() > 1)
	{
		// elements are overlapping
		int topPosZ = -1;
		for (UIElement* target : targets)
		{
			if (target->DepthIndex > topPosZ)
			{
				targetElement = target;
				topPosZ = target->DepthIndex;
			}
		}
	}

	return targetElement;
}

void EventManager::invokeMouseClickEventCallback(UIElement* sender, MouseClickEventType eventType)
{
	if (eventType == LeftClick)
		for (callback_function func : sender->GetLeftMouseClickEvents())
			func(sender);

	if (eventType == RightClick)
		for (callback_function func : sender->GetRightMouseClickEvents())
			func(sender);
}

void EventManager::invokeMouseHoverEventCallback(UIElement* sender, MouseHoverEventType eventType)
{
	if (eventType == HoverOn)
		for (callback_function func : sender->GetMouseHoverOnEvents())
			func(sender);

	if (eventType == HoverOff)
		for (callback_function func : sender->GetMouseHoverOffEvents())
			func(sender);
}

void EventManager::invokeMouseDownEventCallback(UIElement* sender)
{
	for (callback_function func : sender->GetMouseDownEvents())
		func(sender);
}

void EventManager::ParseKeypress(KeyEvt evt)
{
	for (UIElement* elem : this->view->GetElements())
	{
		if (!elem->IsFocused()) continue;

		std::vector<keybd_callback_function>& keypressEvts = elem->GetKeyEvents();
		for (keybd_callback_function func : keypressEvts)
			func(elem, evt);
	}
}

void EventManager::ParseScrollWheelsEvent(short scroll_delta)
{
	for (UIElement* elem : view->GetElements())
		for (scrollwhl_callback_function func : elem->GetScrollWheelEvents())
			func(elem, (int)scroll_delta);
}

void EventManager::ParseMouseClickEvent(POINT point, MouseClickEventType eventType)
{
	std::vector<UIElement*> missed_targets;
	std::vector<UIElement*> targets = GetMousePointIntersectionTargets(view->GetElements(), point, missed_targets);

	if (targets.size() == 1)
	{
		UIElement* targetElement = targets.at(0);
		// immidiately call the callback function
		if (targetElement != nullptr)
		{
			invokeMouseClickEventCallback(targetElement, eventType);
			if (!targetElement->IsFocused())
				targetElement->Focus();
		}
	}
	else if (targets.size() > 1)
	{
		// elements are overlapping
		int topPosZ = -1;
		UIElement* targetElement = nullptr;
		for (UIElement* target : targets)
		{
			if (target->DepthIndex > topPosZ)
			{
				targetElement = target;
				topPosZ = target->DepthIndex;
			}
		}

		if (targetElement != nullptr)
		{
			invokeMouseClickEventCallback(targetElement, eventType);
			if (!targetElement->IsFocused())
				targetElement->Focus();
		}
	}

	if (missed_targets.size() > 0)
	{
		for (UIElement* elem : missed_targets)
			if (elem != nullptr && elem->IsFocused())
				elem->Unfocus();
	}
}

void EventManager::ParseMouseMovedEvent(POINT point, UIElement*& previousActiveElement)
{
	std::vector<UIElement*> targets = GetMousePointIntersectionTargets(view->GetElements(), point);

	UIElement* targetElement = GetTopMostElement(targets);

	// detecting hover on/off events
	if (targetElement != previousActiveElement)
	{
		if (previousActiveElement != nullptr)
		{
			// hover off the previous element
			invokeMouseHoverEventCallback(previousActiveElement, HoverOff);
		}

		if (targetElement != nullptr)
		{
			// hover on the new element
			invokeMouseHoverEventCallback(targetElement, HoverOn);
		}
	}

	// update previous element
	previousActiveElement = targetElement;
}

void EventManager::ParseMouseDownEvent(POINT point, bool& mouseAlreadyDownOnElement, UIElement* previousActiveElement, UIElement*& targetElementOut)
{
	std::vector<UIElement*> targets = GetMousePointIntersectionTargets(view->GetElements(), point);

	UIElement* targetElement = GetTopMostElement(targets);
	if (targetElement == previousActiveElement && targetElement != nullptr && mouseAlreadyDownOnElement)
	{
		// call the callback function
		invokeMouseDownEventCallback(targetElement);
	}

	if (targetElement != previousActiveElement)
		mouseAlreadyDownOnElement = false;

	targetElementOut = targetElement;
}
